//------------------------------------------------------------------------------
// File: Transfrm.cpp
//
// Desc: DirectShow base classes - implements class for simple transform
//       filters such as video decompressors.
//
// Copyright (c) 1992-2001 Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------


#include <streams.h>
#include <measure.h>


// =================================================================
// Implements the CTransformFilter class
// =================================================================

CTransformFilter::CTransformFilter(TCHAR     *pName,
                                   LPUNKNOWN pUnk,
                                   REFCLSID  clsid) :
    CBaseFilter(pName,pUnk,&m_csFilter, clsid),
    m_pInput(NULL),
    m_pOutput(NULL),
    m_bEOSDelivered(FALSE),
    m_bQualityChanged(FALSE),
    m_bSampleSkipped(FALSE) {
#ifdef PERF
    RegisterPerfId();
#endif //  PERF
}

#ifdef UNICODE
CTransformFilter::CTransformFilter(char     *pName,
                                   LPUNKNOWN pUnk,
                                   REFCLSID  clsid) :
    CBaseFilter(pName,pUnk,&m_csFilter, clsid),
    m_pInput(NULL),
    m_pOutput(NULL),
    m_bEOSDelivered(FALSE),
    m_bQualityChanged(FALSE),
    m_bSampleSkipped(FALSE) {
#ifdef PERF
    RegisterPerfId();
#endif //  PERF
}
#endif

// destructor

CTransformFilter::~CTransformFilter() {
    // Delete the pins

    delete m_pInput;
    delete m_pOutput;
}


// Transform place holder - should never be called
HRESULT CTransformFilter::Transform(IMediaSample * pIn, IMediaSample *pOut) {
    UNREFERENCED_PARAMETER(pIn);
    UNREFERENCED_PARAMETER(pOut);
    DbgBreak("CTransformFilter::Transform() should never be called");
    return E_UNEXPECTED;
}


// return the number of pins we provide

int CTransformFilter::GetPinCount() {
    return 2;
}


// return a non-addrefed CBasePin * for the user to addref if he holds onto it
// for longer than his pointer to us. We create the pins dynamically when they
// are asked for rather than in the constructor. This is because we want to
// give the derived class an oppportunity to return different pin objects

// We return the objects as and when they are needed. If either of these fails
// then we return NULL, the assumption being that the caller will realise the
// whole deal is off and destroy us - which in turn will delete everything.

CBasePin *
CTransformFilter::GetPin(int n) {
    HRESULT hr = S_OK;

    // Create an input pin if necessary

    if(m_pInput == NULL) {

        m_pInput = new CTransformInputPin(NAME("Transform input pin"),
            this,              // Owner filter
            &hr,               // Result code
            L"XForm In");      // Pin name


        //  Can't fail
        ASSERT(SUCCEEDED(hr));
        if(m_pInput == NULL) {
            return NULL;
        }
        m_pOutput = (CTransformOutputPin *)
            new CTransformOutputPin(NAME("Transform output pin"),
            this,            // Owner filter
            &hr,             // Result code
            L"XForm Out");   // Pin name


        // Can't fail
        ASSERT(SUCCEEDED(hr));
        if(m_pOutput == NULL) {
            delete m_pInput;
            m_pInput = NULL;
        }
    }

    // Return the appropriate pin

    if(n == 0) {
        return m_pInput;
    }
    else
        if(n == 1) {
        return m_pOutput;
    }
    else {
        return NULL;
    }
}


//
// FindPin
//
// If Id is In or Out then return the IPin* for that pin
// creating the pin if need be.  Otherwise return NULL with an error.

STDMETHODIMP CTransformFilter::FindPin(LPCWSTR Id, IPin **ppPin) {
    CheckPointer(ppPin,E_POINTER);
    ValidateReadWritePtr(ppPin,sizeof(IPin *));

    if(0==lstrcmpW(Id,L"In")) {
        *ppPin = GetPin(0);
    }
    else if(0==lstrcmpW(Id,L"Out")) {
        *ppPin = GetPin(1);
    }
    else {
        *ppPin = NULL;
        return VFW_E_NOT_FOUND;
    }

    HRESULT hr = NOERROR;
    //  AddRef() returned pointer - but GetPin could fail if memory is low.
    if(*ppPin) {
        (*ppPin)->AddRef();
    }
    else {
        hr = E_OUTOFMEMORY;  // probably.  There's no pin anyway.
    }
    return hr;
}


// override these two functions if you want to inform something
// about entry to or exit from streaming state.

HRESULT
CTransformFilter::StartStreaming() {
    return NOERROR;
}


HRESULT
CTransformFilter::StopStreaming() {
    return NOERROR;
}


// override this to grab extra interfaces on connection

HRESULT
CTransformFilter::CheckConnect(PIN_DIRECTION dir,IPin *pPin) {
    UNREFERENCED_PARAMETER(dir);
    UNREFERENCED_PARAMETER(pPin);
    return NOERROR;
}


// place holder to allow derived classes to release any extra interfaces

HRESULT
CTransformFilter::BreakConnect(PIN_DIRECTION dir) {
    UNREFERENCED_PARAMETER(dir);
    return NOERROR;
}


// Let derived classes know about connection completion

HRESULT
CTransformFilter::CompleteConnect(PIN_DIRECTION direction,IPin *pReceivePin) {
    UNREFERENCED_PARAMETER(direction);
    UNREFERENCED_PARAMETER(pReceivePin);
    return NOERROR;
}


// override this to know when the media type is really set

HRESULT
CTransformFilter::SetMediaType(PIN_DIRECTION direction,const CMediaType *pmt) {
    UNREFERENCED_PARAMETER(direction);
    UNREFERENCED_PARAMETER(pmt);
    return NOERROR;
}


// Set up our output sample
HRESULT
CTransformFilter::InitializeOutputSample(IMediaSample *pSample, IMediaSample **ppOutSample) {
    IMediaSample *pOutSample;

    // default - times are the same

    AM_SAMPLE2_PROPERTIES * const pProps = m_pInput->SampleProps();
    DWORD dwFlags = m_bSampleSkipped ? AM_GBF_PREVFRAMESKIPPED : 0;

    // This will prevent the image renderer from switching us to DirectDraw
    // when we can't do it without skipping frames because we're not on a
    // keyframe.  If it really has to switch us, it still will, but then we
    // will have to wait for the next keyframe
    if(!(pProps->dwSampleFlags & AM_SAMPLE_SPLICEPOINT)) {
        dwFlags |= AM_GBF_NOTASYNCPOINT;
    }

    ASSERT(m_pOutput->m_pAllocator != NULL);
    HRESULT hr = m_pOutput->m_pAllocator->GetBuffer(&pOutSample
        , pProps->dwSampleFlags & AM_SAMPLE_TIMEVALID ?
        &pProps->tStart : NULL
        , pProps->dwSampleFlags & AM_SAMPLE_STOPVALID ?
        &pProps->tStop : NULL
        , dwFlags);
    *ppOutSample = pOutSample;
    if(FAILED(hr)) {
        return hr;
    }

    ASSERT(pOutSample);
    IMediaSample2 *pOutSample2;
    if(SUCCEEDED(pOutSample->QueryInterface(IID_IMediaSample2,
        (void **)&pOutSample2))) {
        /*  Modify it */
        AM_SAMPLE2_PROPERTIES OutProps;
        EXECUTE_ASSERT(SUCCEEDED(pOutSample2->GetProperties(FIELD_OFFSET(AM_SAMPLE2_PROPERTIES, tStart), (PBYTE)&OutProps)));
        OutProps.dwTypeSpecificFlags = pProps->dwTypeSpecificFlags;
        OutProps.dwSampleFlags =
            (OutProps.dwSampleFlags & AM_SAMPLE_TYPECHANGED) |
            (pProps->dwSampleFlags & ~AM_SAMPLE_TYPECHANGED);

        OutProps.tStart = pProps->tStart;
        OutProps.tStop  = pProps->tStop;
        OutProps.cbData = FIELD_OFFSET(AM_SAMPLE2_PROPERTIES, dwStreamId);

        hr = pOutSample2->SetProperties(FIELD_OFFSET(AM_SAMPLE2_PROPERTIES, dwStreamId),
            (PBYTE)&OutProps);
        if(pProps->dwSampleFlags & AM_SAMPLE_DATADISCONTINUITY) {
            m_bSampleSkipped = FALSE;
        }
        pOutSample2->Release();
    }
    else {
        if(pProps->dwSampleFlags & AM_SAMPLE_TIMEVALID) {
            pOutSample->SetTime(&pProps->tStart,
                &pProps->tStop);
        }
        if(pProps->dwSampleFlags & AM_SAMPLE_SPLICEPOINT) {
            pOutSample->SetSyncPoint(TRUE);
        }
        if(pProps->dwSampleFlags & AM_SAMPLE_DATADISCONTINUITY) {
            pOutSample->SetDiscontinuity(TRUE);
            m_bSampleSkipped = FALSE;
        }
        // Copy the media times

        LONGLONG MediaStart, MediaEnd;
        if(pSample->GetMediaTime(&MediaStart,&MediaEnd) == NOERROR) {
            pOutSample->SetMediaTime(&MediaStart,&MediaEnd);
        }
    }
    return S_OK;
}

// override this to customize the transform process

HRESULT
CTransformFilter::Receive(IMediaSample *pSample) {
    /*  Check for other streams and pass them on */
    AM_SAMPLE2_PROPERTIES * const pProps = m_pInput->SampleProps();
    if(pProps->dwStreamId != AM_STREAM_MEDIA) {
        return m_pOutput->m_pInputPin->Receive(pSample);
    }
    HRESULT hr;
    ASSERT(pSample);
    IMediaSample * pOutSample;

    // If no output to deliver to then no point sending us data

    ASSERT(m_pOutput != NULL) ;

    // Set up the output sample
    hr = InitializeOutputSample(pSample, &pOutSample);

    if(FAILED(hr)) {
        return hr;
    }

    // Start timing the transform (if PERF is defined)
    MSR_START(m_idTransform);

    // have the derived class transform the data

    hr = Transform(pSample, pOutSample);

    // Stop the clock and log it (if PERF is defined)
    MSR_STOP(m_idTransform);

    if(FAILED(hr)) {
        DbgLog((LOG_TRACE,1,TEXT("Error from transform")));
    }
    else {
        // the Transform() function can return S_FALSE to indicate that the
        // sample should not be delivered; we only deliver the sample if it's
        // really S_OK (same as NOERROR, of course.)
        if(hr == NOERROR) {
            hr = m_pOutput->m_pInputPin->Receive(pOutSample);
            m_bSampleSkipped = FALSE;   // last thing no longer dropped
        }
        else {
            // S_FALSE returned from Transform is a PRIVATE agreement
            // We should return NOERROR from Receive() in this cause because returning S_FALSE
            // from Receive() means that this is the end of the stream and no more data should
            // be sent.
            if(S_FALSE == hr) {

                //  Release the sample before calling notify to avoid
                //  deadlocks if the sample holds a lock on the system
                //  such as DirectDraw buffers do
                pOutSample->Release();
                m_bSampleSkipped = TRUE;
                if(!m_bQualityChanged) {
                    NotifyEvent(EC_QUALITY_CHANGE,0,0);
                    m_bQualityChanged = TRUE;
                }
                return NOERROR;
            }
        }
    }

    // release the output buffer. If the connected pin still needs it,
    // it will have addrefed it itself.
    pOutSample->Release();

    return hr;
}


// Return S_FALSE to mean "pass the note on upstream"
// Return NOERROR (Same as S_OK)
// to mean "I've done something about it, don't pass it on"
HRESULT CTransformFilter::AlterQuality(Quality q) {
    UNREFERENCED_PARAMETER(q);
    return S_FALSE;
}


// EndOfStream received. Default behaviour is to deliver straight
// downstream, since we have no queued data. If you overrode Receive
// and have queue data, then you need to handle this and deliver EOS after
// all queued data is sent
HRESULT
CTransformFilter::EndOfStream(void) {
    HRESULT hr = NOERROR;
    if(m_pOutput != NULL) {
        hr = m_pOutput->DeliverEndOfStream();
    }

    return hr;
}


// enter flush state. Receives already blocked
// must override this if you have queued data or a worker thread
HRESULT
CTransformFilter::BeginFlush(void) {
    HRESULT hr = NOERROR;
    if(m_pOutput != NULL) {
        // block receives -- done by caller (CBaseInputPin::BeginFlush)

        // discard queued data -- we have no queued data

        // free anyone blocked on receive - not possible in this filter

        // call downstream
        hr = m_pOutput->DeliverBeginFlush();
    }
    return hr;
}


// leave flush state. must override this if you have queued data
// or a worker thread
HRESULT
CTransformFilter::EndFlush(void) {
    // sync with pushing thread -- we have no worker thread

    // ensure no more data to go downstream -- we have no queued data

    // call EndFlush on downstream pins
    ASSERT(m_pOutput != NULL);
    return m_pOutput->DeliverEndFlush();

    // caller (the input pin's method) will unblock Receives
}


// override these so that the derived filter can catch them

STDMETHODIMP
CTransformFilter::Stop() {
    CAutoLock lck1(&m_csFilter);
    if(m_State == State_Stopped) {
        return NOERROR;
    }

    // Succeed the Stop if we are not completely connected

    ASSERT(m_pInput == NULL || m_pOutput != NULL);
    if(m_pInput == NULL || m_pInput->IsConnected() == FALSE ||
        m_pOutput->IsConnected() == FALSE) {
        m_State = State_Stopped;
        m_bEOSDelivered = FALSE;
        return NOERROR;
    }

    ASSERT(m_pInput);
    ASSERT(m_pOutput);

    // decommit the input pin before locking or we can deadlock
    m_pInput->Inactive();

    // synchronize with Receive calls

    CAutoLock lck2(&m_csReceive);
    m_pOutput->Inactive();

    // allow a class derived from CTransformFilter
    // to know about starting and stopping streaming

    HRESULT hr = StopStreaming();
    if(SUCCEEDED(hr)) {
        // complete the state transition
        m_State = State_Stopped;
        m_bEOSDelivered = FALSE;
    }
    return hr;
}


STDMETHODIMP
CTransformFilter::Pause() {
    CAutoLock lck(&m_csFilter);
    HRESULT hr = NOERROR;

    if(m_State == State_Paused) {
        // (This space left deliberately blank)
    }

    // If we have no input pin or it isn't yet connected then when we are
    // asked to pause we deliver an end of stream to the downstream filter.
    // This makes sure that it doesn't sit there forever waiting for
    // samples which we cannot ever deliver without an input connection.

    else if(m_pInput == NULL || m_pInput->IsConnected() == FALSE) {
        if(m_pOutput && m_bEOSDelivered == FALSE) {
            m_pOutput->DeliverEndOfStream();
            m_bEOSDelivered = TRUE;
        }
        m_State = State_Paused;
    }

    // We may have an input connection but no output connection
    // However, if we have an input pin we do have an output pin

    else if(m_pOutput->IsConnected() == FALSE) {
        m_State = State_Paused;
    }
    else {
        if(m_State == State_Stopped) {
            // allow a class derived from CTransformFilter
            // to know about starting and stopping streaming
            CAutoLock lck2(&m_csReceive);
            hr = StartStreaming();
        }
        if(SUCCEEDED(hr)) {
            hr = CBaseFilter::Pause();
        }
    }

    m_bSampleSkipped = FALSE;
    m_bQualityChanged = FALSE;
    return hr;
}

HRESULT
CTransformFilter::NewSegment(
    REFERENCE_TIME tStart,
    REFERENCE_TIME tStop,
    double dRate) {
    if(m_pOutput != NULL) {
        return m_pOutput->DeliverNewSegment(tStart, tStop, dRate);
    }
    return S_OK;
}

// Check streaming status
HRESULT
CTransformInputPin::CheckStreaming() {
    ASSERT(m_pTransformFilter->m_pOutput != NULL);
    if(!m_pTransformFilter->m_pOutput->IsConnected()) {
        return VFW_E_NOT_CONNECTED;
    }
    else {
        //  Shouldn't be able to get any data if we're not connected!
        ASSERT(IsConnected());

        //  we're flushing
        if(m_bFlushing) {
            return S_FALSE;
        }
        //  Don't process stuff in Stopped state
        if(IsStopped()) {
            return VFW_E_WRONG_STATE;
        }
        if(m_bRunTimeError) {
            return VFW_E_RUNTIME_ERROR;
        }
        return S_OK;
    }
}


// =================================================================
// Implements the CTransformInputPin class
// =================================================================


// constructor

CTransformInputPin::CTransformInputPin(
    TCHAR *pObjectName,
    CTransformFilter *pTransformFilter,
    HRESULT * phr,
    LPCWSTR pName)
    : CBaseInputPin(pObjectName, pTransformFilter, &pTransformFilter->m_csFilter, phr, pName) {
    DbgLog((LOG_TRACE,2,TEXT("CTransformInputPin::CTransformInputPin")));
    m_pTransformFilter = pTransformFilter;
}

#ifdef UNICODE
CTransformInputPin::CTransformInputPin(
    CHAR *pObjectName,
    CTransformFilter *pTransformFilter,
    HRESULT * phr,
    LPCWSTR pName)
    : CBaseInputPin(pObjectName, pTransformFilter, &pTransformFilter->m_csFilter, phr, pName) {
    DbgLog((LOG_TRACE,2,TEXT("CTransformInputPin::CTransformInputPin")));
    m_pTransformFilter = pTransformFilter;
}
#endif

// provides derived filter a chance to grab extra interfaces

HRESULT
CTransformInputPin::CheckConnect(IPin *pPin) {
    HRESULT hr = m_pTransformFilter->CheckConnect(PINDIR_INPUT,pPin);
    if(FAILED(hr)) {
        return hr;
    }
    return CBaseInputPin::CheckConnect(pPin);
}


// provides derived filter a chance to release it's extra interfaces

HRESULT
CTransformInputPin::BreakConnect() {
    //  Can't disconnect unless stopped
    ASSERT(IsStopped());
    m_pTransformFilter->BreakConnect(PINDIR_INPUT);
    return CBaseInputPin::BreakConnect();
}


// Let derived class know when the input pin is connected

HRESULT
CTransformInputPin::CompleteConnect(IPin *pReceivePin) {
    HRESULT hr = m_pTransformFilter->CompleteConnect(PINDIR_INPUT,pReceivePin);
    if(FAILED(hr)) {
        return hr;
    }
    return CBaseInputPin::CompleteConnect(pReceivePin);
}


// check that we can support a given media type

HRESULT
CTransformInputPin::CheckMediaType(const CMediaType* pmt) {
    // Check the input type

    HRESULT hr = m_pTransformFilter->CheckInputType(pmt);
    if(S_OK != hr) {
        return hr;
    }

    // if the output pin is still connected, then we have
    // to check the transform not just the input format

    if((m_pTransformFilter->m_pOutput != NULL) &&
        (m_pTransformFilter->m_pOutput->IsConnected())) {
        return m_pTransformFilter->CheckTransform(pmt,
            &m_pTransformFilter->m_pOutput->CurrentMediaType());
    }
    else {
        return hr;
    }
}


// set the media type for this connection

HRESULT
CTransformInputPin::SetMediaType(const CMediaType* mtIn) {
    // Set the base class media type (should always succeed)
    HRESULT hr = CBasePin::SetMediaType(mtIn);
    if(FAILED(hr)) {
        return hr;
    }

    // check the transform can be done (should always succeed)
    ASSERT(SUCCEEDED(m_pTransformFilter->CheckInputType(mtIn)));

    return m_pTransformFilter->SetMediaType(PINDIR_INPUT,mtIn);
}


// =================================================================
// Implements IMemInputPin interface
// =================================================================


// provide EndOfStream that passes straight downstream
// (there is no queued data)
STDMETHODIMP
CTransformInputPin::EndOfStream(void) {
    CAutoLock lck(&m_pTransformFilter->m_csReceive);
    HRESULT hr = CheckStreaming();
    if(S_OK == hr) {
        hr = m_pTransformFilter->EndOfStream();
    }
    return hr;
}


// enter flushing state. Call default handler to block Receives, then
// pass to overridable method in filter
STDMETHODIMP
CTransformInputPin::BeginFlush(void) {
    CAutoLock lck(&m_pTransformFilter->m_csFilter);
    //  Are we actually doing anything?
    ASSERT(m_pTransformFilter->m_pOutput != NULL);
    if(!IsConnected() ||
        !m_pTransformFilter->m_pOutput->IsConnected()) {
        return VFW_E_NOT_CONNECTED;
    }
    HRESULT hr = CBaseInputPin::BeginFlush();
    if(FAILED(hr)) {
        return hr;
    }

    return m_pTransformFilter->BeginFlush();
}


// leave flushing state.
// Pass to overridable method in filter, then call base class
// to unblock receives (finally)
STDMETHODIMP
CTransformInputPin::EndFlush(void) {
    CAutoLock lck(&m_pTransformFilter->m_csFilter);
    //  Are we actually doing anything?
    ASSERT(m_pTransformFilter->m_pOutput != NULL);
    if(!IsConnected() ||
        !m_pTransformFilter->m_pOutput->IsConnected()) {
        return VFW_E_NOT_CONNECTED;
    }

    HRESULT hr = m_pTransformFilter->EndFlush();
    if(FAILED(hr)) {
        return hr;
    }

    return CBaseInputPin::EndFlush();
}


// here's the next block of data from the stream.
// AddRef it yourself if you need to hold it beyond the end
// of this call.

HRESULT
CTransformInputPin::Receive(IMediaSample * pSample) {
    HRESULT hr;
    CAutoLock lck(&m_pTransformFilter->m_csReceive);
    ASSERT(pSample);

    // check all is well with the base class
    hr = CBaseInputPin::Receive(pSample);
    if(S_OK == hr) {
        hr = m_pTransformFilter->Receive(pSample);
    }
    return hr;
}




// override to pass downstream
STDMETHODIMP
CTransformInputPin::NewSegment(
    REFERENCE_TIME tStart,
    REFERENCE_TIME tStop,
    double dRate) {
    //  Save the values in the pin
    CBasePin::NewSegment(tStart, tStop, dRate);
    return m_pTransformFilter->NewSegment(tStart, tStop, dRate);
}




// =================================================================
// Implements the CTransformOutputPin class
// =================================================================


// constructor

CTransformOutputPin::CTransformOutputPin(
    TCHAR *pObjectName,
    CTransformFilter *pTransformFilter,
    HRESULT * phr,
    LPCWSTR pPinName)
    : CBaseOutputPin(pObjectName, pTransformFilter, &pTransformFilter->m_csFilter, phr, pPinName),
      m_pPosition(NULL) {
    DbgLog((LOG_TRACE,2,TEXT("CTransformOutputPin::CTransformOutputPin")));
    m_pTransformFilter = pTransformFilter;

}

#ifdef UNICODE
CTransformOutputPin::CTransformOutputPin(
    CHAR *pObjectName,
    CTransformFilter *pTransformFilter,
    HRESULT * phr,
    LPCWSTR pPinName)
    : CBaseOutputPin(pObjectName, pTransformFilter, &pTransformFilter->m_csFilter, phr, pPinName),
      m_pPosition(NULL) {
    DbgLog((LOG_TRACE,2,TEXT("CTransformOutputPin::CTransformOutputPin")));
    m_pTransformFilter = pTransformFilter;

}
#endif

// destructor

CTransformOutputPin::~CTransformOutputPin() {
    DbgLog((LOG_TRACE,2,TEXT("CTransformOutputPin::~CTransformOutputPin")));

    if(m_pPosition) m_pPosition->Release();
}


// overriden to expose IMediaPosition and IMediaSeeking control interfaces

STDMETHODIMP
CTransformOutputPin::NonDelegatingQueryInterface(REFIID riid, void **ppv) {
    CheckPointer(ppv,E_POINTER);
    ValidateReadWritePtr(ppv,sizeof(PVOID));
    *ppv = NULL;

    if(riid == IID_IMediaPosition || riid == IID_IMediaSeeking) {

        // we should have an input pin by now

        ASSERT(m_pTransformFilter->m_pInput != NULL);

        if(m_pPosition == NULL) {

            HRESULT hr = CreatePosPassThru(GetOwner(),
                FALSE,
                (IPin *)m_pTransformFilter->m_pInput,
                &m_pPosition);
            if(FAILED(hr)) {
                return hr;
            }
        }
        return m_pPosition->QueryInterface(riid, ppv);
    }
    else {
        return CBaseOutputPin::NonDelegatingQueryInterface(riid, ppv);
    }
}


// provides derived filter a chance to grab extra interfaces

HRESULT
CTransformOutputPin::CheckConnect(IPin *pPin) {
    // we should have an input connection first

    ASSERT(m_pTransformFilter->m_pInput != NULL);
    if((m_pTransformFilter->m_pInput->IsConnected() == FALSE)) {
        return E_UNEXPECTED;
    }

    HRESULT hr = m_pTransformFilter->CheckConnect(PINDIR_OUTPUT,pPin);
    if(FAILED(hr)) {
        return hr;
    }
    return CBaseOutputPin::CheckConnect(pPin);
}


// provides derived filter a chance to release it's extra interfaces

HRESULT
CTransformOutputPin::BreakConnect() {
    //  Can't disconnect unless stopped
    ASSERT(IsStopped());
    m_pTransformFilter->BreakConnect(PINDIR_OUTPUT);
    return CBaseOutputPin::BreakConnect();
}


// Let derived class know when the output pin is connected

HRESULT
CTransformOutputPin::CompleteConnect(IPin *pReceivePin) {
    HRESULT hr = m_pTransformFilter->CompleteConnect(PINDIR_OUTPUT,pReceivePin);
    if(FAILED(hr)) {
        return hr;
    }
    return CBaseOutputPin::CompleteConnect(pReceivePin);
}


// check a given transform - must have selected input type first

HRESULT
CTransformOutputPin::CheckMediaType(const CMediaType* pmtOut) {
    // must have selected input first
    ASSERT(m_pTransformFilter->m_pInput != NULL);
    if((m_pTransformFilter->m_pInput->IsConnected() == FALSE)) {
        return E_INVALIDARG;
    }

    return m_pTransformFilter->CheckTransform(&m_pTransformFilter->m_pInput->CurrentMediaType(),
        pmtOut);
}


// called after we have agreed a media type to actually set it in which case
// we run the CheckTransform function to get the output format type again

HRESULT
CTransformOutputPin::SetMediaType(const CMediaType* pmtOut) {
    HRESULT hr = NOERROR;
    ASSERT(m_pTransformFilter->m_pInput != NULL);

    ASSERT(m_pTransformFilter->m_pInput->CurrentMediaType().IsValid());

    // Set the base class media type (should always succeed)
    hr = CBasePin::SetMediaType(pmtOut);
    if(FAILED(hr)) {
        return hr;
    }

#ifdef DEBUG
    if(FAILED(m_pTransformFilter->CheckTransform(&m_pTransformFilter->
        m_pInput->CurrentMediaType(),pmtOut))) {
        DbgLog((LOG_ERROR,0,TEXT("*** This filter is accepting an output media type")));
        DbgLog((LOG_ERROR,0,TEXT("    that it can't currently transform to.  I hope")));
        DbgLog((LOG_ERROR,0,TEXT("    it's smart enough to reconnect its input.")));
    }
#endif

    return m_pTransformFilter->SetMediaType(PINDIR_OUTPUT,pmtOut);
}


// pass the buffer size decision through to the main transform class

HRESULT
CTransformOutputPin::DecideBufferSize(
    IMemAllocator * pAllocator,
    ALLOCATOR_PROPERTIES* pProp) {
    return m_pTransformFilter->DecideBufferSize(pAllocator, pProp);
}



// return a specific media type indexed by iPosition

HRESULT
CTransformOutputPin::GetMediaType(
    int iPosition,
    CMediaType *pMediaType) {
    ASSERT(m_pTransformFilter->m_pInput != NULL);

    //  We don't have any media types if our input is not connected

    if(m_pTransformFilter->m_pInput->IsConnected()) {
        return m_pTransformFilter->GetMediaType(iPosition,pMediaType);
    }
    else {
        return VFW_S_NO_MORE_ITEMS;
    }
}


// Override this if you can do something constructive to act on the
// quality message.  Consider passing it upstream as well

// Pass the quality mesage on upstream.

STDMETHODIMP
CTransformOutputPin::Notify(IBaseFilter * pSender, Quality q) {
    UNREFERENCED_PARAMETER(pSender);
    ValidateReadPtr(pSender,sizeof(IBaseFilter));

    // First see if we want to handle this ourselves
    HRESULT hr = m_pTransformFilter->AlterQuality(q);
    if(hr!=S_FALSE) {
        return hr;        // either S_OK or a failure
    }

    // S_FALSE means we pass the message on.
    // Find the quality sink for our input pin and send it there

    ASSERT(m_pTransformFilter->m_pInput != NULL);

    return m_pTransformFilter->m_pInput->PassNotify(q);

} // Notify


// the following removes a very large number of level 4 warnings from the microsoft
// compiler output, which are not useful at all in this case.
#pragma warning(disable:4514)

